﻿/******************************************************************************
 * 
 * Announce: CSharpKit, Basic algorithms, components and definitions.
 *           Copyright (C) ShenYongchen.
 *           All rights reserved.
 *   Author: 申永辰.郑州 (shenyczz@163.com)
 *  WebSite: http://github.com/shenyczz/CSharpKit
 *
 * THIS CODE IS LICENSED UNDER THE MIT LICENSE (MIT).
 * THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF 
 * ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
 * IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
 * PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
 * 
******************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime;
using System.Text;

namespace CSharpKit.Numerics.LinearAlgebra
{
    /// <summary>
    /// 矢量
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [Serializable]
    public abstract partial class Vector<T> : Numeric<T>, IFormattable, ICloneable<Vector<T>>, IEquatable<Vector<T>>//, IList, IList<T>
        where T : struct, IFormattable, IEquatable<T>
    {
        #region Constructors

        protected Vector(VectorStorage<T> storage)
        {
            _storage = storage;
        }

        #endregion


        public static readonly VectorBuilder<T> Builder = BuilderInstance<T>.VectorBuilder;


        #region Storage - 存储

        protected VectorStorage<T> _storage;
        public VectorStorage<T> Storage => _storage;

        #endregion

        #region Length

        public int Count => _storage.Length;
        public int Length => _storage.Length;

        #endregion

        #region Indexer

        public T this[int index]
        {
            get { return Storage[index]; }
            set { Storage[index] = value; }
        }

        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
        internal T At(int index)
        {
            return Storage.At(index);
        }

        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
        internal void At(int index, T value)
        {
            Storage.At(index, value);
        }

        #endregion


        #region ICloneable<Vector<T>>

        public Vector<T> Clone()
        {
            var result = Builder.SameAs(this);
            Storage.CopyToUnchecked(result.Storage, ExistingData.AssumeZeros);
            return result;
        }

        #endregion

        #region IEquatable<Vector<T>>

        public bool Equals(Vector<T> other)
        {
            return other != null
                && Storage.Equals(other.Storage);
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as Vector<T>);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode()
                ^ Storage.GetHashCode();
        }

        #endregion

        #region IFormattable Members

        public sealed override string ToString()
        {
            return ToString("G6");
        }
        public string ToString(string format, IFormatProvider formatProvider = null)
        {
#if DEBUG
            var vtemp = string.Concat(ToTypeString(), Environment.NewLine, ToVectorString(format, formatProvider));
#endif
            return string.Concat(ToTypeString(), Environment.NewLine, ToVectorString(format, formatProvider));
        }

        protected string ToTypeString()
        {
            return string.Format("{0} {1}-{2}", GetType().Name, Count, typeof(T).Name);
        }

        protected string ToVectorString(string format, IFormatProvider provider = null)
        {
            if (string.IsNullOrEmpty(format))
            {
                format = "G6";
            }

            return ToVectorString(12, 80, "..", "  ", Environment.NewLine, x => x.ToString(format, provider));
        }
        
        public string ToVectorString(int maxPerColumn, int maxCharactersWidth, string format = null, IFormatProvider provider = null)
        {
            if (format == null)
            {
                format = "G6";
            }

            return ToVectorString(maxPerColumn, maxCharactersWidth, "..", "  ", Environment.NewLine, x => x.ToString(format, provider));
        }

        protected string ToVectorString(int maxPerColumn, int maxCharactersWidth, string ellipsis, string columnSeparator, string rowSeparator, Func<T, string> formatValue)
        {
            return FormatStringArrayToString(
                ToVectorStringArray(maxPerColumn, maxCharactersWidth, columnSeparator.Length, ellipsis, formatValue),
                columnSeparator, rowSeparator);
        }

        protected string[,] ToVectorStringArray(int maxPerColumn, int maxCharactersWidth, int padding, string ellipsis, Func<T, string> formatValue)
        {
            // enforce minima to avoid pathetic cases
            maxPerColumn = System.Math.Max(maxPerColumn, 3);
            maxCharactersWidth = System.Math.Max(maxCharactersWidth, 16);

            var columns = new List<Tuple<int, string[]>>();
            int chars = 0;
            int offset = 0;
            while (offset < Count)
            {
                // full column
                int height = System.Math.Min(maxPerColumn, Count - offset);
                var candidate = FormatCompleteColumn(offset, height, formatValue);
                chars += candidate.Item1 + padding;
                if (chars > maxCharactersWidth && offset > 0)
                {
                    break;
                }
                columns.Add(candidate);
                offset += height;
            }
            if (offset < Count)
            {
                // we're not done yet, but adding the last column has failed
                // --> make the last column partial
                var last = columns[columns.Count - 1];
                var c = last.Item2;
                c[c.Length - 2] = ellipsis;
                c[c.Length - 1] = formatValue(this[Count - 1]);
            }

            int rows = columns[0].Item2.Length;
            int cols = columns.Count;
            var array = new string[rows, cols];
            int colIndex = 0;
            foreach (var column in columns)
            {
                for (int k = 0; k < column.Item2.Length; k++)
                {
                    array[k, colIndex] = column.Item2[k];
                }
                for (int k = column.Item2.Length; k < rows; k++)
                {
                    array[k, colIndex] = "";
                }
                colIndex++;
            }
            return array;
        }

        private Tuple<int, string[]> FormatCompleteColumn(int offset, int height, Func<T, string> formatValue)
        {
            var c = new string[height];
            int index = 0;
            for (var k = 0; k < height; k++)
            {
                c[index++] = formatValue(this[offset + k]);
            }
            int w = c.Max(x => x.Length);
            return new Tuple<int, string[]>(w, c);
        }

        private string FormatStringArrayToString(string[,] array, string columnSeparator, string rowSeparator)
        {
            var rows = array.GetLength(0);
            var cols = array.GetLength(1);

            var widths = new int[cols];
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < cols; j++)
                {
                    widths[j] = System.Math.Max(widths[j], array[i, j].Length);
                }
            }

            var sb = new StringBuilder();
            for (int i = 0; i < rows; i++)
            {
                sb.Append(array[i, 0].PadLeft(widths[0]));
                for (int j = 1; j < cols; j++)
                {
                    sb.Append(columnSeparator);
                    sb.Append(array[i, j].PadLeft(widths[j]));
                }
                sb.Append(rowSeparator);
            }
            return sb.ToString();
        }

        #endregion


        #region Clearing

        public void Clear()
        {
            Storage.Clear();
        }

        public void ClearSubVector(int index, int count)
        {
            if (count < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(count), Resources.Numerics.ArgumentMustBePositive);
            }

            if (index + count > Count || index < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }

            Storage.Clear(index, count);
        }

        public void CoerceZero(Func<T, bool> zeroPredicate)
        {
            MapInplace(x => zeroPredicate(x) ? Zero : x, Zeros.AllowSkip);
        }
        public virtual void CoerceZero(T threshold)
        {
            MapInplace(x => NumOperators.LessThan(x, threshold) ? Zero : x, Zeros.AllowSkip);
        }

        #endregion


        #region Copying

        public void CopyTo(Vector<T> target)
        {
            if (target == null)
            {
                throw new ArgumentNullException(nameof(target));
            }

            Storage.CopyTo(target.Storage);
        }

        public void SetValues(T[] values)
        {
            var source = new VectorStorageDense<T>(Count, values);
            source.CopyTo(Storage);
        }

        #endregion


        #region Enumerating

        public IEnumerable<T> Enumerate()
        {
            return Storage.Enumerate();
        }
        public IEnumerable<T> Enumerate(Zeros zeros = Zeros.Include)
        {
            switch (zeros)
            {
                case Zeros.AllowSkip:
                    return Storage.EnumerateNonZero();

                default:
                    return Storage.Enumerate();
            }
        }

        public IEnumerable<Tuple<int, T>> EnumerateIndexed()
        {
            return Storage.EnumerateIndexed();
        }
        public IEnumerable<Tuple<int, T>> EnumerateIndexed(Zeros zeros = Zeros.Include)
        {
            switch (zeros)
            {
                case Zeros.AllowSkip:
                    return Storage.EnumerateNonZeroIndexed();

                default:
                    return Storage.EnumerateIndexed();
            }
        }


        #endregion


        #region Extracting

        public T[] ToArray()
        {
            return Storage.ToArray();
        }

        public Matrix<T> ToColumnMatrix()
        {
            var result = Matrix<T>.Builder.SameAs(this, Count, 1);
            Storage.CopyToColumnUnchecked(result.Storage, 0, ExistingData.AssumeZeros);
            return result;
        }

        public Matrix<T> ToRowMatrix()
        {
            var result = Matrix<T>.Builder.SameAs(this, 1, Count);
            Storage.CopyToRowUnchecked(result.Storage, 0, ExistingData.AssumeZeros);
            return result;
        }

        #endregion

        #region Extracting - SubVector

        public Vector<T> SubVector(int index, int count)
        {
            var target = Builder.SameAs(this, count);
            Storage.CopySubVectorTo(target.Storage, index, 0, count, ExistingData.AssumeZeros);
            return target;
        }
        public void SetSubVector(int index, int count, Vector<T> subVector)
        {
            if (subVector == null)
            {
                throw new ArgumentNullException(nameof(subVector));
            }

            subVector.Storage.CopySubVectorTo(Storage, 0, index, count);
        }
        public void CopySubVectorTo(Vector<T> destination, int sourceIndex, int targetIndex, int count)
        {
            if (destination == null)
            {
                throw new ArgumentNullException(nameof(destination));
            }

            // TODO: refactor range checks
            Storage.CopySubVectorTo(destination.Storage, sourceIndex, targetIndex, count);
        }

        #endregion


        #region Finding

        public Tuple<int, T> Find(Func<T, bool> predicate, Zeros zeros = Zeros.AllowSkip)
        {
            return Storage.Find(predicate, zeros);
        }
        public Tuple<int, T, TOther> Find2<TOther>(Func<T, TOther, bool> predicate, Vector<TOther> other, Zeros zeros = Zeros.AllowSkip)
            where TOther : struct, IEquatable<TOther>, IFormattable
        {
            return Storage.Find2(other.Storage, predicate, zeros);
        }

        public bool Exists(Func<T, bool> predicate, Zeros zeros = Zeros.AllowSkip)
        {
            return Storage.Find(predicate, zeros) != null;
        }
        public bool Exists2<TOther>(Func<T, TOther, bool> predicate, Vector<TOther> other, Zeros zeros = Zeros.AllowSkip)
            where TOther : struct, IEquatable<TOther>, IFormattable
        {
            return Storage.Find2(other.Storage, predicate, zeros) != null;
        }
        public bool ForAll(Func<T, bool> predicate, Zeros zeros = Zeros.AllowSkip)
        {
            return Storage.Find(x => !predicate(x), zeros) == null;
        }
        public bool ForAll2<TOther>(Func<T, TOther, bool> predicate, Vector<TOther> other, Zeros zeros = Zeros.AllowSkip)
            where TOther : struct, IEquatable<TOther>, IFormattable
        {
            return Storage.Find2(other.Storage, (x, y) => !predicate(x, y), zeros) == null;
        }

        #endregion


        #region COMBINATORS - Maping

        public void Map(Func<T, T> fun, Vector<T> result, Zeros zeros = Zeros.AllowSkip)
        {
            if (ReferenceEquals(this, result))
            {
                Storage.MapInplace(fun, zeros);
            }
            else
            {
                Storage.MapTo(result.Storage, fun, zeros, (zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear));
            }
        }
        public void MapIndexed(Func<int, T, T> f, Vector<T> result, Zeros zeros = Zeros.AllowSkip)
        {
            if (ReferenceEquals(this, result))
            {
                Storage.MapIndexedInplace(f, zeros);
            }
            else
            {
                Storage.MapIndexedTo(result.Storage, f, zeros, zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear);
            }
        }


        public void MapConvert<TU>(Func<T, TU> f, Vector<TU> result, Zeros zeros = Zeros.AllowSkip)
            where TU : struct, IEquatable<TU>, IFormattable
        {
            Storage.MapTo(result.Storage, f, zeros, zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear);
        }
        public void MapIndexedConvert<TU>(Func<int, T, TU> f, Vector<TU> result, Zeros zeros = Zeros.AllowSkip)
            where TU : struct, IEquatable<TU>, IFormattable
        {
            Storage.MapIndexedTo(result.Storage, f, zeros, zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear);
        }


        public Vector<TU> Map<TU>(Func<T, TU> f, Zeros zeros = Zeros.AllowSkip)
            where TU : struct, IEquatable<TU>, IFormattable
        {
            var result = Vector<TU>.Builder.SameAs(this);
            Storage.MapToUnchecked(result.Storage, f, zeros, ExistingData.AssumeZeros);
            return result;
        }
        public Vector<TU> MapIndexed<TU>(Func<int, T, TU> f, Zeros zeros = Zeros.AllowSkip)
            where TU : struct, IEquatable<TU>, IFormattable
        {
            var result = Vector<TU>.Builder.SameAs(this);
            Storage.MapIndexedToUnchecked(result.Storage, f, zeros, ExistingData.AssumeZeros);
            return result;
        }


        public void MapInplace(Func<T, T> f, Zeros zeros = Zeros.AllowSkip)
        {
            Storage.MapInplace(f, zeros);
        }
        public void MapIndexedInplace(Func<int, T, T> f, Zeros zeros = Zeros.AllowSkip)
        {
            Storage.MapIndexedInplace(f, zeros);
        }


        public Vector<T> Map2(Func<T, T, T> f, Vector<T> other, Zeros zeros = Zeros.AllowSkip)
        {
            var result = Builder.SameAs(this);
            Storage.Map2To(result.Storage, other.Storage, f, zeros, ExistingData.AssumeZeros);
            return result;
        }
        public void Map2(Func<T, T, T> f, Vector<T> other, Vector<T> result, Zeros zeros = Zeros.AllowSkip)
        {
            Storage.Map2To(result.Storage, other.Storage, f, zeros, ExistingData.Clear);
        }


        #endregion

        #region COMBINATORS - Folding

        /// <summary>
        /// Applies a function to update the status with each value pair of two vectors and returns the resulting status.
        /// </summary>
        public TState Fold2<TOther, TState>(Func<TState, T, TOther, TState> f, TState state, Vector<TOther> other, Zeros zeros = Zeros.AllowSkip)
            where TOther : struct, IEquatable<TOther>, IFormattable
        {
            return Storage.Fold2(other.Storage, f, state, zeros);
        }

        #endregion


        //}}@@@
    }



}

